package com.devo.icecream.modbus

import android.app.Application
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import com.devo.icecream.AppLogger
import com.devo.icecream.utils.ByteUtil
import com.serotonin.modbus4j.BatchRead
import com.serotonin.modbus4j.BatchResults
import com.serotonin.modbus4j.ModbusFactory
import com.serotonin.modbus4j.ModbusMaster
import com.serotonin.modbus4j.code.DataType
import com.serotonin.modbus4j.exception.ModbusInitException
import com.serotonin.modbus4j.exception.ModbusTransportException
import com.serotonin.modbus4j.ip.IpParameters
import com.serotonin.modbus4j.ip.tcp.TcpMaster
import com.serotonin.modbus4j.locator.BaseLocator
import com.serotonin.modbus4j.msg.*
import com.serotonin.modbus4j.sero.log.IOLog
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import okhttp3.internal.toHexString
import java.io.File

internal fun ModbusMaster.trySend(request: ModbusRequest): ModbusResponse? {
    return try {
        send(request)
    } catch (e: ModbusTransportException) {
        AppLogger.e(e, "发送 modbus 命令出错")
        null
    }
}

internal fun <T> ModbusMaster.trySend(batchRead: BatchRead<T>): BatchResults<T>? {
    return try {
        send(batchRead)
    } catch (e: ModbusTransportException) {
        AppLogger.e(e, "发送 modbus 命令出错")
        null
    }
}


object PLCManager {

    private const val HOST = "192.168.6.6"
    private const val PORT = 502

    private val factory: ModbusFactory = ModbusFactory()
    lateinit var tcpMaster: TcpMaster
    private val workThread: HandlerThread by lazy {
        val thread = HandlerThread("plc_work")
        thread.start()
        thread
    }
    private val workHandler: Handler by lazy {
        Handler(workThread.looper)
    }
    private val mainHandler: Handler = Handler(Looper.getMainLooper())
    //温度  PLC运行状态  库存的总报警  plc运行错误标识 等查询
    private var status: PLCStatus? = null
    private var stock: Stock? = null
    //plc故障状态获取 这个类的功能主要是查询plc工控机故障的状态
    private var malfunction: PLCMalfunction? = null

    private val connectStatusFlow = MutableStateFlow<PLCConnectStatus>(PLCConnectStatus.None)

    val plcStatus: PLCStatus?
        get() = status

    val plcStock: Stock?
        get() = stock
    //plc故障状态获取
    val plcMalfunction: PLCMalfunction?
        get() = malfunction

    fun init(context: Application) {
        val parameters = IpParameters()
        parameters.host = HOST
        parameters.port = PORT
        parameters.isEncapsulated = false
        //连接plc
        tcpMaster = factory.createTcpMaster(parameters, true) as TcpMaster
        workHandler.post(object : Runnable {
            override fun run() {
                try {
                    tcpMaster.timeout = 15 * 1000
                    tcpMaster.retries = 3
                    tcpMaster.ioLog =
                        IOLog(context.filesDir.absolutePath + File.separator + "xxx.log")
                    //plc初始化
                    tcpMaster.init()
                } catch (e: ModbusInitException) {
                    AppLogger.e(e, "init: Can't initial modbus")
                    mainHandler.post {
                        //连接失败状态
                        connectStatusFlow.tryEmit(PLCConnectStatus.Disconnected);
                    }
                    workHandler.postDelayed(this, 3_000)
                }
            }
        })
        //发送心跳
        startHeartBeat()
        //温度  PLC运行状态  库存的总报警  plc运行错误标识 等查询
        status = PLCStatus(tcpMaster, workHandler)
        //状态查询，用料查询等
        stock = Stock(tcpMaster, workHandler)
        //检查plc故障
        malfunction = PLCMalfunction(tcpMaster, workHandler)
    }

    private fun startHeartBeat() {
        workHandler.post(object : Runnable {
            lateinit var response: ReadHoldingRegistersResponse;
            override fun run() {
                try {
                    response = tcpMaster.send(
                        ReadHoldingRegistersRequest(
                            1,
                            9018,
                            2
                        )
                    ) as ReadHoldingRegistersResponse
                    val value = ByteUtil.bytesToIntBySwapped(response.data)
                    connectStatusFlow.tryEmit(if (value == 1) PLCConnectStatus.Connected else PLCConnectStatus.Disconnected)
                } catch (e: Exception) {
                    connectStatusFlow.tryEmit(PLCConnectStatus.Disconnected)
                    AppLogger.e(e, "PLC 心跳发送失败")
                }
                workHandler.postDelayed(this, 5_000)
            }
        })
    }

    fun close() {
        workHandler.post { tcpMaster.destroy() }
        workHandler.removeCallbacksAndMessages(null)
        workThread.interrupt()
    }

    fun make(recipe: Recipe, listener: MakeListener) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            //如果是主线程，切换到子线程去制作
            workHandler.post {
                realMake(recipe, listener)
            }
        } else {
            realMake(recipe, listener)
        }
    }

    //很多地方需要查询PLC连接状态
    val connectionStatus: Flow<PLCConnectStatus>
        get() = connectStatusFlow

    //告诉后端plc是否断开  PLC 断开连接，告诉后端
    fun isAvailable(): Boolean {
        return connectStatusFlow.value == PLCConnectStatus.Connected
    }

    private fun realMake(recipe: Recipe, listener: MakeListener) {
        val premakeAddress = 8006
        try {
            AppLogger.i("PLC 开始制作")
            waitForReady(tcpMaster)
            recipe.install(tcpMaster)

            val makeCmd = listOf(1, 1).map { ByteUtil.intToShortArray(it).toList() }.flatten()

            // 写入开始预制作和制作指令
            val makerResponse =
                tcpMaster.send(
                    WriteRegistersRequest(
                        1,
                        premakeAddress,
                        makeCmd.toShortArray()
                    )
                ) as WriteRegistersResponse

            AppLogger.w("制作命令 response : ${makerResponse.numberOfRegisters.toHexString()}")

            // 没有在制作中，检查原因
            if (!checkMaking(tcpMaster)) {
                val request = ReadHoldingRegistersRequest(1, Address.MAKE_RESULTS, 2)
                val response = tcpMaster.send(request) as ReadHoldingRegistersResponse
                val code = ByteUtil.bytesToIntBySwapped(response.data)
                AppLogger.e("没有开始制作，code：$code")
                //返回状态码
                mainHandler.post { listener.onMake(false, code) }
                return
            }
            waitMakeFinished(tcpMaster, listener)
        } catch (e: ModbusTransportException) {
            AppLogger.e(e, "make method: ice cream is cannot make")
        }
    }

    @Throws(ModbusTransportException::class)
    private fun waitForReady(master: ModbusMaster) {
        var isReturn = false
        //9002 PLC运行状态 0:调试中 1.准备中 2:等待制作指令 3:正在制作中 4:设备故障
        val request = ReadHoldingRegistersRequest(1, 9002, 2)
        var response: ReadHoldingRegistersResponse
        while (!isReturn) {
            response = master.send(request) as ReadHoldingRegistersResponse

            isReturn = ByteUtil.bytesToIntBySwapped(response.data) == 2
            AppLogger.w("等待中...$response")
            try {
                Thread.sleep((3 * 1000).toLong())
            } catch (e: InterruptedException) {
                AppLogger.e(e, "waitForReady: ")
            }
        }
        System.gc()
    }

    private fun waitMakeFinished(master: TcpMaster, listener: MakeListener) {
        workHandler.postDelayed({
            when (checkMakeStatus(master)) {// 0等待中，1制作完成，2故障
                // 正常
                1 -> {
                    val result = getMakeCode(master)    //返回制作返回的制作码  0等待中，1正常完成，2料不足退款，3故障维修
                    mainHandler.post {

                        listener.onMake(result == 1, result)
                    }
                    return@postDelayed
                }
                // 故障
                2, 3 -> {
                    val result = getMakeCode(master)  //返回制作返回的制作码  0等待中，1正常完成，2料不足退款，3故障维修
                    mainHandler.post {
                        listener.onMake(false, result)
                    }
                    return@postDelayed
                }
            }
            waitMakeFinished(master, listener)
        }, (3 * 1000).toLong())
    }

    private fun checkMaking(master: TcpMaster): Boolean {
        val plcStatusAddress = 9002
        var retry = 3
        val request = ReadHoldingRegistersRequest(1, plcStatusAddress, 2)
        while (retry > 0) {
            try {
                val results = master.send(request) as ReadHoldingRegistersResponse
                val status = ByteUtil.bytesToIntBySwapped(results.data)
                AppLogger.w("正在制作:$status")
                return status == 3;
            } catch (e: Exception) {
                e.printStackTrace()
                retry--
            }
        }
        return false
    }

    /**
     *
     * @param master TcpMaster
     * @return Int 0等待中，1正常完成，2料不足退款，3故障维修
     */
    private fun getMakeCode(master: TcpMaster): Int {
        val makeResultsAddress = 9012
        var retry = 3

        while (retry > 0) {

            try {
                val request = ReadHoldingRegistersRequest(1, makeResultsAddress, 2)
                val response: ReadHoldingRegistersResponse =
                    master.send(request) as ReadHoldingRegistersResponse
                AppLogger.w("制作结果：${ByteUtil.ByteArrToHex(response.data)}");
                return ByteUtil.bytesToIntBySwapped(response.data)
            } catch (e: Exception) {
                AppLogger.e(e, "获取制作结果出错")
                retry--
            }
        }
        return -1
    }

    /**
     *
     * @param master TcpMaster
     * @return Int 0等待中，1制作完成，2故障
     */
    private fun checkMakeStatus(master: TcpMaster): Int {
        val makeResultsAddress = 9010
        try {

            val request = ReadHoldingRegistersRequest(1, makeResultsAddress, 2)
            val response: ReadHoldingRegistersResponse =
                master.send(request) as ReadHoldingRegistersResponse
            AppLogger.w("检查制作结果：${ByteUtil.ByteArrToHex(response.data)}");
            return ByteUtil.bytesToIntBySwapped(response.data)
        } catch (e: Exception) {
            AppLogger.e(e, "检查制作结果出错")
        }
        return -1
    }

    fun watchCupStatus(listener: CupListener) {
        workHandler.postDelayed(
            {
                try {
                    val request = ReadHoldingRegistersRequest(1, 9014, 2)
                    val response: ReadHoldingRegistersResponse =
                        tcpMaster.send(request) as ReadHoldingRegistersResponse
                    val status: Int = ByteUtil.bytesToIntBySwapped(response.data)
                    if (status != 2) {
                        watchCupStatus(listener)
                    }
                    mainHandler.post {
                        listener.onOutFinish(status)
                    }
                } catch (e: ModbusTransportException) {
                    e.printStackTrace()
                }
            },
            (3 * 1000).toLong(),
        )
    }


    //查询真实配方
    fun queryRealRecipe(map: Map<Int, Int>, onResults: (Map<Int, Float>) -> Unit) {
        workHandler.post {

            val t = System.currentTimeMillis()
            val queries = BatchRead<String>().apply {
                var index = 0
                map.forEach {
                    addLocator(
                        "${t}-${index++}",
                        BaseLocator.holdingRegister(1, it.value, DataType.FOUR_BYTE_FLOAT_SWAPPED)
                    )
                }
            }

            while (true) {
                try {
                    tcpMaster.send(queries).let { rets ->
                        var index = 0
                        val resultsMap = map.keys.associateWith {
                            val v = rets.getFloatValue("${t}-${index++}")
                            v
                        }
                        AppLogger.i("检查实际用量：$resultsMap")
                        mainHandler.post {
                            onResults(resultsMap)
                        }
                    }
                    break
                } catch (e: ModbusTransportException) {
                    AppLogger.e(e, "查询真实用量报错")
                }
            }

        }

    }

    fun checkStock(serial: Int): Boolean {
        return !stock!!.needSupply(serial)
    }

    fun checkPLCStatus(): Boolean {
        return status!!.checkStatus()
    }

    interface MakeListener {
        /**
         * @param success 是否成功
         * @param code    0等待中，1正常完成，2料不足退款，3故障维修
         */
        fun onMake(success: Boolean, code: Int)
    }

    interface CupListener {
        fun onOutFinish(status: Int)
    }
}

sealed class PLCConnectStatus {
    object None : PLCConnectStatus()
    object Connected : PLCConnectStatus()
    object Disconnected : PLCConnectStatus()
}
